package com.element.security.server.global.token;

import com.element.security.constant.SecurityParams;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.HttpRequestMethodNotSupportedException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @auther zhangwj
 * @date 2021/6/24 上午9:57
 */
public class GlobalClientTokenEndpointFilter extends AbstractAuthenticationProcessingFilter {

    private final static String PATH = "/oauth/token";

    private final AuthorizationServerSecurityConfigurer configurer;

    private AuthenticationEntryPoint authenticationEntryPoint;

    private final boolean allowOnlyPost;

    public GlobalClientTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer) {
        this(PATH, configurer);
    }

    public GlobalClientTokenEndpointFilter(String path, AuthorizationServerSecurityConfigurer configurer) {
        super(path);
        this.configurer = configurer;
        this.authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
        this.allowOnlyPost = false;
        this.setRequiresAuthenticationRequestMatcher(new GlobalClientTokenEndpointFilter.ClientCredentialsRequestMatcher(path));
        ((OAuth2AuthenticationEntryPoint) this.authenticationEntryPoint).setTypeName("Form");
    }

//    public void setAllowOnlyPost(boolean allowOnlyPost) {
//        this.allowOnlyPost = allowOnlyPost;
//    }

    public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
        this.authenticationEntryPoint = authenticationEntryPoint;
    }


    @Override
    protected AuthenticationManager getAuthenticationManager() {
        return configurer.and().getSharedObject(AuthenticationManager.class);
    }

    @Override
    public void afterPropertiesSet() {
        // super.afterPropertiesSet(); 去掉不需要验证 authenticationManager
        this.setAuthenticationFailureHandler((request, response, exception) -> {
            if (exception instanceof BadCredentialsException) {
                exception = new BadCredentialsException(exception.getMessage(), new BadClientCredentialsException());
            }
            GlobalClientTokenEndpointFilter.this.authenticationEntryPoint.commence(request, response, exception);
        });
        this.setAuthenticationSuccessHandler((request, response, authentication) -> {
        });
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, ServletException {
        if (this.allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) {
            throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[]{"POST"});
        } else {
            String clientId = request.getParameter(SecurityParams.CLIENT_ID);
            String clientSecret = request.getParameter(SecurityParams.CLIENT_SECRET);
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication != null && authentication.isAuthenticated()) {
                return authentication;
            } else if (clientId == null) {
                throw new BadCredentialsException("No client credentials presented");
            } else {
                if (clientSecret == null) {
                    clientSecret = "";
                }
                clientId = clientId.trim();
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId, clientSecret);
                AuthenticationManager authenticationManager = this.getAuthenticationManager();
                return authenticationManager.authenticate(authRequest);
            }
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        super.successfulAuthentication(request, response, chain, authResult);
        chain.doFilter(request, response);
    }

    protected static class ClientCredentialsRequestMatcher implements RequestMatcher {
        private final String path;

        public ClientCredentialsRequestMatcher(String path) {
            this.path = path;
        }

        public boolean matches(HttpServletRequest request) {
            String uri = request.getRequestURI();
            int pathParamIndex = uri.indexOf(59);
            if (pathParamIndex > 0) {
                uri = uri.substring(0, pathParamIndex);
            }
            String clientId = request.getParameter(SecurityParams.CLIENT_ID);
            if (clientId == null) {
                return true;
            } else {
                return "".equals(request.getContextPath()) ? uri.endsWith(this.path) : uri.endsWith(request.getContextPath() + this.path);
            }
        }
    }
}
